/* 
 * File:   main.c
 * Author: alaskov
 *
 * Created on Penktadienis, 2013, Sausio 25, 13.52
 */

#include <xc.h>
#include <sys/attribs.h>
#include <plib.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "../tnkernel_2_5_908/tnkernel_2_5_908/source/tnkernel/tnkernel.h"

#include "config.h"
#include "hardware.h"
#include "lcddevport.h"
#include "usbotg.h"
#include "cdc_descriptors.h"
#include "pic32mxusbotg.h"


//#pragma config FSRSSEL = PRIORITY_7, FMIIEN = OFF
#pragma config FWDTEN = OFF
#pragma config FUSBIDIO = ON
#pragma config FVBUSONIO = ON
#pragma config FPLLIDIV = DIV_1, FPLLMUL = MUL_24, FPLLODIV = DIV_2
#pragma config FNOSC = PRIPLL
#pragma config POSCMOD = XT
#pragma config FSOSCEN = OFF, IESO = OFF, OSCIOFNC = OFF
#pragma config FPBDIV = DIV_2 //Peripheral Clock Divisor Pb_Clk is Sys_Clk/2
#pragma config DEBUG = ON,ICESEL = ICS_PGx3
#pragma config JTAGEN = OFF

// USN CLOCK
#pragma config UPLLEN = ON
#pragma config UPLLIDIV = DIV_1


#define PIC32_SOFTWARE_BREAK()  __asm__ volatile ("sdbbp 0")

int PERIPHERAL_CLOCK = 0;
int SYSTICS = 0;

TN_TCB*  task_MAIN = NULL;
TN_TCB*  task_usb = NULL;
TN_DQUE* queue_MAIN = NULL;
TN_EVENT* event_USB_CDC_READY = NULL;
TN_EVENT* event_USB_CDC_IN = NULL;
TN_EVENT* event_USB_CDC_OUT = NULL;
TN_FMP* fmp_EVENTS = NULL;

static void task_MAIN_func(void * par);
static void task_usb_func(void * par);

void tn_app_init(void);
void int_conf(void);
void idle_handler(void);

static void cdc_init_handler(void);
static void cdc_data_received_handler(void);
static void cdc_data_transmited_handler(void);

tn_sys_interrupt(_CORE_TIMER_VECTOR)
{
   // clear the interrupt flag
    INTClearFlag(INT_CT);

	SYSTICS += (1000/TOGGLES_PER_SEC);

    // .. things to do
    tn_tick_int_processing();

    // update the period
    UpdateCoreTimer(CORE_TICK_RATE);
}

tn_sys_interrupt(_USB_1_VECTOR)
{
  usb_handler();

  ClearAllUsbInterruptFlags();
  ClearAllUsbErrorInterruptFlags();
  ClearGlobalUsbInterruptFlag();
}

int main()
{
  PERIPHERAL_CLOCK = SYSTEMConfigPerformance(SYS_FREQ);
  INTDisableInterrupts();
  INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR);

  config_pr_io();

// configure the core timer roll-over rate (10msec)
  OpenCoreTimer(CORE_TICK_RATE);

  // The Core timer should halt when we are halted at a debug breakpoint.
  _CP0_BIC_DEBUG(_CP0_DEBUG_COUNTDM_MASK);

  // set up the core timer interrupt with a priority of 6 and zero sub-priority
  mConfigIntCoreTimer(CT_INT_ON | CT_INT_PRIOR_6 | CT_INT_SUB_PRIOR_0);
  
  tn_start_system((TN_UWORD*)calloc(128, sizeof(TN_UWORD)), 128,
				  (TN_UWORD*)calloc(128, sizeof(TN_UWORD)), 128,
				  tn_app_init,
				  int_conf,
				  idle_handler);

  return (EXIT_SUCCESS);
}

void tn_app_init()
{
  InitHdwr();

  task_MAIN = malloc(sizeof(TN_TCB));
  task_usb = malloc(sizeof(TN_TCB));
  queue_MAIN = malloc(sizeof(TN_DQUE));
  event_USB_CDC_READY = malloc(sizeof(TN_EVENT));
  event_USB_CDC_IN = malloc(sizeof(TN_EVENT));
  event_USB_CDC_OUT = malloc(sizeof(TN_EVENT));
  fmp_EVENTS = malloc(sizeof(TN_FMP));
    
  queue_MAIN->id_dque = 0;
  tn_queue_create(queue_MAIN, calloc(EVENT_QUEUE_DEPTH, sizeof(void*)), EVENT_QUEUE_DEPTH);

  event_USB_CDC_READY->id_event = 0;
  tn_event_create(event_USB_CDC_READY, TN_EVENT_ATTR_SINGLE | TN_EVENT_ATTR_CLR, 0);

  event_USB_CDC_IN->id_event = 0;
  tn_event_create(event_USB_CDC_IN, TN_EVENT_ATTR_SINGLE | TN_EVENT_ATTR_CLR, 0);

  event_USB_CDC_OUT->id_event = 0;
  tn_event_create(event_USB_CDC_OUT, TN_EVENT_ATTR_SINGLE | TN_EVENT_ATTR_CLR, 0);

  fmp_EVENTS->id_fmp = 0;
  tn_fmem_create(fmp_EVENTS, calloc(EVENT_QUEUE_DEPTH * 20 / sizeof(int), sizeof(int)),  20, EVENT_QUEUE_DEPTH);

  task_MAIN->id_task = 0;
  tn_task_create(task_MAIN,                  //-- task TCB
                 task_MAIN_func,               //-- task function
                 DEF_TASK_PRIORITY,           //-- task priority
                 calloc(TASK_MAIN_STK_SIZE, sizeof(unsigned int)),             //-- task stack first addr in memory
                 TASK_MAIN_STK_SIZE,           //-- task stack size (in words)
                 TN_NULL,                //-- task function parameter
                 TN_TASK_START_ON_CREATION  //-- Creation option
                 );

   task_usb->id_task = 0;
   tn_task_create(task_usb,                  //-- task TCB
                 task_usb_func,               //-- task function
                 2,           //-- task priority
                 calloc(DEF_STACK_SZ, sizeof(unsigned int)),             //-- task stack first addr in memory
                 DEF_STACK_SZ,           //-- task stack size (in words)
                 TN_NULL,                //-- task function parameter
                 TN_TASK_DORMANT_ON_CREATION  //-- Creation option
                 );
   
}

void int_conf(void)
{
    // set up the software interrupt 0 with a priority of 1, subpriority 0
  INTSetVectorPriority(INT_CORE_SOFTWARE_0_VECTOR, INT_PRIORITY_LEVEL_1);
  INTSetVectorSubPriority(INT_CORE_SOFTWARE_0_VECTOR, INT_SUB_PRIORITY_LEVEL_0);
  INTClearFlag(INT_CT);
  INTEnable(INT_CS0, INT_ENABLED);

  INTEnableInterrupts();
}




/**
 * ???????, ??????? ???????????? ?????????? ?? ?????? IDLE
 *
 */
void idle_handler (void)
{
  PowerSaveIdle();
}

static void task_MAIN_func(void *par)
{
  init_pmp();

  lcd_open_dev(FOUR_BIT & LINES_5X7);

  setvbuf(stdout, calloc(STDIO_BUFF_SZ, sizeof(char)), _IOFBF , STDIO_BUFF_SZ);
  setvbuf(stderr, calloc(STDIO_BUFF_SZ, sizeof(char)), _IOFBF , STDIO_BUFF_SZ);

  lcd_write_ram_addr(0);

  fputs("READY", stdout);
  fflush(stdout);

  usb_init(cdc_device_descriptor,
		   cdc_config_descriptor,
		   cdc_str_descs,
		   NULL, //cdc_dev_qualifier_descs,
		   4,
		   USB_DEVICE_SELF_POWERED | USB_DEVICE_FEATURE_WAKEUP);

  initCDC();

  cdc_register_startup_handler(cdc_init_handler);
  cdc_register_data_received(cdc_data_received_handler);
  cdc_register_data_transmited(cdc_data_transmited_handler);

  usb_start();

  SetUsbGlobalInterruptPriority(INT_PRIORITY_LEVEL_6, INT_SUB_PRIORITY_LEVEL_3);
  EnableUsbPerifInterrupts(USB_URST | USB_UERR | USB_TRN | USB_IDLE | USB_RESUME);\
  ClearAllUsbInterruptFlags();
  ClearAllUsbErrorInterruptFlags();
  EnableUsbGlobalInterrupt();

  lcd_write_ram_addr(LINE3_ADDR);
  fputs("USB started", stdout);
  fflush(stdout);

//  tn_sys_exit_critical();

  tn_task_activate(task_usb);

  void* data = NULL;
  while(tn_queue_receive(queue_MAIN, &data, TN_WAIT_INFINITE) == TERR_NO_ERR)
  {
      lcd_write_ram_addr(LINE1_ADDR);
	  fputs((char*)data, stdout);
	  //fprintf(stdout, "%d", f);
	  fflush(stdout);

	  tn_fmem_release(fmp_EVENTS, data);
//	}
  }
}

static void cdc_init_handler(void)
{
  tn_event_iset(event_USB_CDC_READY, 1);
}

static void cdc_data_received_handler(void)
{
  tn_event_iset(event_USB_CDC_OUT, 1);
}

static void cdc_data_transmited_handler(void)
{
  tn_event_iset(event_USB_CDC_IN, 1);
}

static void usb_in_wait_handler(void)
{
  unsigned int pattern = 0;
  tn_event_wait(event_USB_CDC_IN, 1, TN_EVENT_WCOND_OR, &pattern, TN_WAIT_INFINITE);
}

static void usb_out_wait_handler(void)
{
  unsigned int pattern = 0;
  tn_event_wait(event_USB_CDC_OUT, 1, TN_EVENT_WCOND_OR, &pattern, TN_WAIT_INFINITE);
}

static void task_usb_func(void * par)
{
  unsigned char buff[64];
  unsigned int pattern = 0;
  void* data;

  do
  {
	tn_event_wait(event_USB_CDC_READY, 1, TN_EVENT_WCOND_OR, &pattern, TN_WAIT_INFINITE);

	if (tn_fmem_get_polling(fmp_EVENTS, &data) == TERR_NO_ERR)
	{
	  strcpy(data, "CDC started");

	  if (tn_queue_send_polling(queue_MAIN, data) != TERR_NO_ERR)
		tn_fmem_release(fmp_EVENTS, data);
	}

    int n = readCDCarray(buff, sizeof(buff), usb_out_wait_handler);
	while(n >= 0)
	{
      if (writeCDCarray(buff, n, usb_in_wait_handler) < 0)
	    break;

	  if (n > 0)
	  {
		if (tn_fmem_get_polling(fmp_EVENTS, &data) == TERR_NO_ERR)
		{
		  memset(data, 0, 20);
		  memcpy(data, buff, n > 16 ? 16 : n);
		  
		  if (tn_queue_send_polling(queue_MAIN, data) != TERR_NO_ERR)
			tn_fmem_release(fmp_EVENTS, data);
		}
	  }

  	  n = readCDCarray(buff, sizeof(buff), usb_out_wait_handler);
	}

	if (tn_fmem_get_polling(fmp_EVENTS, &data) == TERR_NO_ERR)
	{
	  strcpy(data, "CDC RESET");

	  if (tn_queue_send_polling(queue_MAIN, data) != TERR_NO_ERR)
		tn_fmem_release(fmp_EVENTS, data);
	}
  } while(true);
}

static enum {
      EXCEP_IRQ = 0,            // interrupt
      EXCEP_AdEL = 4,            // address error exception (load or ifetch)
      EXCEP_AdES,                // address error exception (store)
      EXCEP_IBE,                // bus error (ifetch)
      EXCEP_DBE,                // bus error (load/store)
      EXCEP_Sys,                // syscall
      EXCEP_Bp,                // breakpoint
      EXCEP_RI,                // reserved instruction
      EXCEP_CpU,                // coprocessor unusable
      EXCEP_Overflow,            // arithmetic overflow
      EXCEP_Trap,                // trap (possible divide by zero)
      EXCEP_IS1 = 16,            // implementation specfic 1
      EXCEP_CEU,                // CorExtend Unuseable
      EXCEP_C2E                // coprocessor 2
  } _excep_code;

static unsigned int _epc_code;
static unsigned int _excep_addr;

static const char* ExceptionName(unsigned int code)
{
  switch(code)
  {
	case EXCEP_IRQ: return "IRQ";
	case EXCEP_AdEL: return "AdEL";
	case EXCEP_AdES: return "AdES";
	case EXCEP_IBE: return "IBE";
	case EXCEP_DBE: return "DBE";
	case EXCEP_Sys: return "Sys";
	case EXCEP_Bp:  return "Bp";
	case EXCEP_RI:  return "RI";
	case EXCEP_CpU: return "CpU";
	case EXCEP_Overflow: return "Ovfl";
	case EXCEP_Trap: return "Trap";
	case EXCEP_IS1:  return "IS1";
	case EXCEP_CEU:  return "CEU";
	case EXCEP_C2E:  return "C2E";
	default: return "";
  }
}

void __attribute__((naked, nomips16, noreturn)) _general_exception_handler(void)
{
    _epc_code=  _CP0_GET_CAUSE() & 0x0000007C >> 2;
    _excep_addr=_CP0_GET_EPC();

//    printf("EX %s %X\n", ExceptionName(_epc_code), _excep_addr);

    PIC32_SOFTWARE_BREAK();
    for (;;) ;
}

void __attribute__((naked, nomips16, noreturn)) _general_exception_context(void)
{
	_epc_code=  _CP0_GET_CAUSE() & 0x0000007C >> 2;
    _excep_addr=_CP0_GET_EPC();

    //printf("EX %s %X\n", ExceptionName(_epc_code), _excep_addr);
	 
    PIC32_SOFTWARE_BREAK();
    for (;;) ;
}


